/*
 * @brief PMU example
 *
 * @note
 * Copyright(C) NXP Semiconductors, 2013
 * All rights reserved.
 *
 * @par
 * Software that is described herein is for illustrative purposes only
 * which provides customers with programming information regarding the
 * LPC products.  This software is supplied "AS IS" without any warranties of
 * any kind, and NXP Semiconductors and its licensor disclaim any and
 * all warranties, express or implied, including all implied warranties of
 * merchantability, fitness for a particular purpose and non-infringement of
 * intellectual property rights.  NXP Semiconductors assumes no responsibility
 * or liability for the use of the software, conveys no license or rights under any
 * patent, copyright, mask work right, or any other intellectual property rights in
 * or to any products. NXP Semiconductors reserves the right to make changes
 * in the software without notification. NXP Semiconductors also makes no
 * representation or warranty that such application will be suitable for the
 * specified use without further testing or modification.
 *
 * @par
 * Permission to use, copy, modify, and distribute this software and its
 * documentation is hereby granted, under NXP Semiconductors' and its
 * licensor's relevant copyrights in the software, without fee, provided that it
 * is used in conjunction with NXP Semiconductors microcontrollers.  This
 * copyright, permission, and disclaimer notice must appear in all copies of
 * this code.
 */

#include "board.h"
#include "stopwatch.h"

/*****************************************************************************
 * Private types/enumerations/variables
 ****************************************************************************/

#if defined(CHIP_LPC1347)
/* Wakeup using PININT interrupt on this GPIO pin */
#define GPIO_PININT_PIN     19	/* GPIO pin number mapped to PININT */
#define GPIO_PININT_PORT    1	/* GPIO port number mapped to PININT */
#define GPIO_PININT_INDEX   0	/* PININT index used for GPIO mapping */
#define PININT_IRQ_HANDLER  PIN_INT0_IRQHandler	/* GPIO interrupt IRQ function name */
#define PININT_NVIC_NAME    PIN_INT0_IRQn	/* GPIO interrupt NVIC interrupt name */

#elif defined(CHIP_LPC1343)
#define DPD_WAKEUP_PIN		IOCON_PIO1_4	/* Pin used for wakeup from deep power down */
#define DPD_IOCON_CFG		(IOCON_FUNC0 | IOCON_MODE_PULLUP |\
							IOCON_RESERVED_BIT_6 | IOCON_RESERVED_BIT_7)
#define GPIO_WAKEUP_PIN		IOCON_PIO0_7	/* Pin used for wakeup from sleep and power down */
#define GPIO_IOCON_CFG		(IOCON_FUNC0 | IOCON_MODE_PULLUP |\
							IOCON_RESERVED_BIT_6 | IOCON_RESERVED_BIT_7)
#define GPIO_WK_PORT		0
#define GPIO_WK_PIN			7
#define GPIO_NVIC_NAME		WAKEUP0_IRQn	/* GPIO interrupt for sleep and power down */
#endif

/* Index of PMU GP registers */
#define PWR_CYCLE_COUNT_REG_INDEX 0

#define DEBOUNCE_DELAY_MS 1000

/*****************************************************************************
 * Public types/enumerations/variables
 ****************************************************************************/

/*****************************************************************************
 * Private functions
 ****************************************************************************/

/* Read or reset the power-cycle counter */
static uint32_t ProcessCycleCounter(void)
{
	uint32_t returnVal = 0xFFFFFFFF;

	/* Read current power-cycle count register */
	returnVal = Chip_PMU_ReadGPREG(LPC_PMU, PWR_CYCLE_COUNT_REG_INDEX);

	/* Write power-cycle count register */
	Chip_PMU_WriteGPREG(LPC_PMU, PWR_CYCLE_COUNT_REG_INDEX, returnVal + 1);

	/* Return current value of cycle-count */
	return returnVal;
}

/* Advance to the next low-power-mode */
static void ProcessPowerState(CHIP_PMU_MCUPOWER_T crntPowerSetting)
{
	volatile uint32_t tempTimeout;

	/* Output power status message, add separating space */
	DEBUGSTR("\r\n");

	/* Switch on current selected power setting */
	switch (crntPowerSetting) {
	case PMU_MCU_SLEEP:
	default:
		DEBUGSTR("-----------------------------------------------------------------\r\n");
		DEBUGSTR("     Entering SLEEP power setting\r\n");
		DEBUGSTR("-----------------------------------------------------------------\r\n\r\n");

		/* Delay to allow all bytes to send out UART and to debounce GPIO interrupt pin. */
		StopWatch_DelayMs(DEBOUNCE_DELAY_MS);

		/* Enter MCU Sleep mode */
		Chip_PMU_SleepState(LPC_PMU);

		break;

	case PMU_MCU_DEEP_SLEEP:
		DEBUGSTR("-----------------------------------------------------------------\r\n");
		DEBUGSTR("     Entering DEEP SLEEP power setting\r\n");
		DEBUGSTR("-----------------------------------------------------------------\r\n\r\n");

		/* Delay to allow all bytes to send out UART and to debounce GPIO interrupt pin. */
		StopWatch_DelayMs(DEBOUNCE_DELAY_MS);

		/* We can optionally call Chip_SYSCTL_SetDeepSleepPD() to power down the
		   BOD and WDT if we aren't using them in deep sleep modes. */
		Chip_SYSCTL_SetDeepSleepPD(SYSCTL_DEEPSLP_BOD_PD | SYSCTL_DEEPSLP_WDTOSC_PD);

		/* We should call Chip_SYSCTL_SetWakeup() to setup any peripherals we want
		   to power back up on wakeup. For this example, we'll power back up the IRC,
		   FLASH, the system oscillator, and the PLL */
		Chip_SYSCTL_SetWakeup(~(SYSCTL_SLPWAKE_IRCOUT_PD | SYSCTL_SLPWAKE_IRC_PD |
								SYSCTL_SLPWAKE_FLASH_PD | SYSCTL_SLPWAKE_SYSOSC_PD | SYSCTL_SLPWAKE_SYSPLL_PD));

		/* Enter MCU Deep Sleep mode */
		Chip_PMU_DeepSleepState(LPC_PMU);

		/* Power anything back up here that isn't powered up on wakeup. The example
		   code below powers back up the BOD and WDT oscillator, which weren't setup to
		   power up in the Chip_SYSCTL_SetWakeup() function. */
		Chip_SYSCTL_SetDeepSleepPD(0);

		break;

#if defined(CHIP_LPC1347)
	case PMU_MCU_POWER_DOWN:
		DEBUGSTR("-----------------------------------------------------------------\r\n");
		DEBUGSTR("     Entering POWER DOWN power setting\r\n");
		DEBUGSTR("-----------------------------------------------------------------\r\n\r\n");

		/* Delay to allow all bytes to send out UART and to debounce GPIO interrupt pin. */
		StopWatch_DelayMs(DEBOUNCE_DELAY_MS);

		/* We can optionally call Chip_SYSCTL_SetDeepSleepPD() to power down the
		   BOD and WDT if we aren't using them in deep sleep modes. */
		Chip_SYSCTL_SetDeepSleepPD(SYSCTL_DEEPSLP_BOD_PD | SYSCTL_DEEPSLP_WDTOSC_PD);

		/* We should call Chip_SYSCTL_SetWakeup() to setup any peripherals we want
		   to power back up on wakeup. For this example, we'll power back up the IRC,
		   FLASH, the system oscillator, and the PLL */
		Chip_SYSCTL_SetWakeup(~(SYSCTL_SLPWAKE_IRCOUT_PD | SYSCTL_SLPWAKE_IRC_PD |
								SYSCTL_SLPWAKE_FLASH_PD | SYSCTL_SLPWAKE_SYSOSC_PD | SYSCTL_SLPWAKE_SYSPLL_PD));

		/* Enter MCU Power down mode */
		Chip_PMU_PowerDownState(LPC_PMU);

		break;
#endif

	case PMU_MCU_DEEP_PWRDOWN:
		DEBUGSTR("-----------------------------------------------------------------\r\n");
		DEBUGSTR("     Entering DEEP POWER DOWN power setting\r\n");
		DEBUGSTR("-----------------------------------------------------------------\r\n\r\n");

		/* Delay to allow all bytes to send out UART and to debounce GPIO interrupt pin. */
		StopWatch_DelayMs(DEBOUNCE_DELAY_MS);

		/* Enter MCU Deep Power down mode */
		Chip_PMU_DeepPowerDownState(LPC_PMU);

		break;
	}
}

/*****************************************************************************
 * Public functions
 ****************************************************************************/

/**
 * @brief	Handle interrupt from GPIO pin or GPIO pin mapped to PININT (LCP1347)
 * @return	Nothing
 */
#if defined(CHIP_LPC1347)
void PININT_IRQ_HANDLER(void)
{
	Chip_PININT_ClearIntStatus(LPC_PININT, PININTCH(GPIO_PININT_INDEX));
}
#endif

/**
 * @brief	Handle interrupt from GPIO pin used for sleep/powerdown wakeup
 * @return	Nothing
 */
#if defined(CHIP_LPC1343)
void WAKEUP_IRQHandler(void)
{
	/* Clear GPIO wakeup */
	Chip_GPIO_ClearInts(LPC_GPIO_PORT, 0, (1 << 0));

	/* Clear wakeup */
	Chip_SYSCTL_ResetStartPin(0, 0);
}
#endif

/**
 * @brief	Main program body
 * @return	int
 */
int main(void)
{
	CHIP_PMU_MCUPOWER_T crntPowerSetting;

	/* Setup SystemCoreClock and any needed board code */
	SystemCoreClockUpdate();
	Board_Init();
	StopWatch_Init();

	/* Start with the LED on */
	Board_LED_Set(0, true);

	/* Output example's activity banner */
	DEBUGSTR("\r\n");
	DEBUGSTR("-----------------------------------------------------------------\r\n");
	DEBUGOUT("Power Control Example   Cycle Count: %d\r\n", ProcessCycleCounter());
	DEBUGSTR("  System will cycle through SLEEP, DEEP SLEEP, POWER\r\n");
	DEBUGSTR("  DOWN, and DEEP POWER DOWN power states\r\n");
	DEBUGSTR("-----------------------------------------------------------------\r\n\r\n");

#if defined(CHIP_LPC1347)
	/* Configure GPIO pin as input */
	Chip_GPIO_SetPinDIRInput(LPC_GPIO_PORT, GPIO_PININT_PORT, GPIO_PININT_PIN);

	/* Configure pin as GPIO with pullup */
	Chip_IOCON_PinMuxSet(LPC_IOCON, GPIO_PININT_PORT, GPIO_PININT_PIN,
						 (IOCON_FUNC0 | IOCON_MODE_PULLUP));

	/* Enable PININT clock */
	Chip_Clock_EnablePeriphClock(SYSCTL_CLOCK_PINT);

	/* Configure interrupt channel for the GPIO pin in SysCon block */
	Chip_SYSCTL_SetPinInterrupt(GPIO_PININT_INDEX, GPIO_PININT_PORT, GPIO_PININT_PIN);

	/* Configure channel interrupt as edge sensitive and falling edge interrupt */
	Chip_PININT_ClearIntStatus(LPC_PININT, PININTCH(GPIO_PININT_INDEX));
	Chip_PININT_SetPinModeEdge(LPC_PININT, PININTCH(GPIO_PININT_INDEX));
	Chip_PININT_EnableIntLow(LPC_PININT, PININTCH(GPIO_PININT_INDEX));

	/* Allow the pin interrupt to wake from deep sleep or power down modes */
	Chip_SYSCTL_EnableStartPin(GPIO_PININT_INDEX);

	/* Enable interrupt in the NVIC */
	NVIC_ClearPendingIRQ(PININT_NVIC_NAME);
	NVIC_EnableIRQ(PININT_NVIC_NAME);

#elif defined(CHIP_LPC1343)
	/* The LPC1343 wakes up from deep power down mode by a low signal on
       the wakeup pin. */
	Chip_PMU_SetPowerDownControl(LPC_PMU, PMU_DPDCTRL_WAKEUPPHYS);

	/* The LPC1343 wakup pin is on P1-4 and requires and external
	   pullup that can be provded via IOCON. */
	Chip_IOCON_PinMuxSet(LPC_IOCON, DPD_WAKEUP_PIN, DPD_IOCON_CFG);

	/* Setup GPIO/wakeup pin for sleep and power down modes */
	Chip_IOCON_PinMuxSet(LPC_IOCON, GPIO_WAKEUP_PIN, GPIO_IOCON_CFG);

	/* GPIO and interrupt setup - must match GPIO_WAKEUP_PIN */
	Chip_GPIO_SetPinDIRInput(LPC_GPIO_PORT, GPIO_WK_PORT, GPIO_WK_PIN);
	Chip_GPIO_SetPinModeEdge(LPC_GPIO_PORT, GPIO_WK_PORT, (1 << GPIO_WK_PIN));
	Chip_GPIO_EnableInt(LPC_GPIO_PORT, GPIO_WK_PORT, (1 << GPIO_WK_PIN));
	Chip_GPIO_ClearInts(LPC_GPIO_PORT, GPIO_WK_PORT, (1 << GPIO_WK_PIN));

	/* wakeup signal setup - must match GPIO_WAKEUP_PIN */
	Chip_SYSCTL_SetStartPinFalling(GPIO_WK_PORT, GPIO_WK_PIN);
	Chip_SYSCTL_EnableStartPin(GPIO_WK_PORT, GPIO_WK_PIN);

	/* Enable interrupt for the GPIO pin */
	NVIC_ClearPendingIRQ(GPIO_NVIC_NAME);
	NVIC_EnableIRQ(GPIO_NVIC_NAME);
#endif

	/* Setup alarm, process next power state then wait for alarm to wake-up system */
	crntPowerSetting = PMU_MCU_SLEEP;

	while (1) {

		/* Enter first (or next) power state */
		ProcessPowerState(crntPowerSetting);

		/* Toggle LED to show activity */
		Board_LED_Toggle(0);

		/* Inc current power setting and test for overflow */
		if (crntPowerSetting == PMU_MCU_DEEP_PWRDOWN) {
			/* Reset to lowest power setting */
			crntPowerSetting = PMU_MCU_SLEEP;
		}
		else {
			crntPowerSetting++;
		}
	}

	return 0;
}
